/*
This file is part of [ahttp] library. 

Author: Artem Kustikov (kustikoff[at]tut.by)


This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.
*/

#ifndef AHTTP_SERVER_H
#define AHTTP_SERVER_H

#pragma once

#include <boost/filesystem.hpp>
#include <boost/noncopyable.hpp>
#include <boost/detail/atomic_count.hpp>

#include "aconnect/server.hpp"
#include "ahttp/http_context.hpp"

namespace ahttp
{
	class HttpServer
	{
	private:
		class HttpServerSettings* _globalSettings;
		
	//==================================================================//
	//	Fields															//
	//==================================================================//
	public:
	
		boost::detail::atomic_count RequestsCount;
		aconnect::Server			Engine;

	public:
		HttpServer();

		HttpServerSettings* GlobalSettings() throw (std::runtime_error) {
			if  (_globalSettings == NULL)
				throw std::runtime_error ("Global HTTP server settings is not loaded");
			if  (_globalSettings->logger() == NULL)
				throw std::runtime_error ("Global HTTP server logger is not initiliazed");

			return _globalSettings;
		}

		inline string_constref getMessage(string_constref key) {
			return GlobalSettings()->getMessage(key);
		}

		inline bool isStopped() {
			return Engine.isStopped();
		}

		aconnect::Logger* Log() throw (std::runtime_error) {
			return GlobalSettings()->logger();
		}

		
		void init (HttpServerSettings* settings);
		

		/**
		* Process HTTP request (and following keep-alive requests on opened socket)
		* @param[in]	client		Filled aconnect::ClientInfo object (with opened socket)
		*/
		void processConnection (const aconnect::ClientInfo& client);

		/**
		* Process HTTP request - full route
		* @param[in/out]	context		Filled HttpContext instance
		*/
		bool processRequest (HttpContext& context);

		/**
		* Process worker creation fail (503 HTTP status will be sent)
		* @param[in]	clientSock		opened client socket
		*/
		void processWorkerCreationError (const aconnect::socket_type clientSock);
		
		void redirectRequest (HttpContext& context,
			string_constref virtualPath, int status = HttpStatus::Found); 

		/**
		* Send 'Page Not Found' message to client
		* @param[in]	context		current HTTP context
		*/
		void processError404 (HttpContext& context);

		void processError403 (HttpContext& context,
			string_constptr message);

		void processError405 (HttpContext& context,
			string_constref allowedMethods);

		void processError406 (HttpContext& context, 
			string_constref message);

		/**
		* Send 'Request Entity Too Large' error page to client
		* @param[in]	context		current HTTP context
		*/
		void processError413 (HttpContext& context);
		
		// send server error response (500+)
		void processServerError (HttpContext& context, 
			int status = HttpStatus::InternalServerError, string_constptr messageFormat = 0, ...);
		
	private:
		
		bool validateRequest (HttpContext& context);
		
		bool findTarget (HttpContext& context);

		void prepareContext (HttpContext &context);

		/**
		* Evaluate and apply 'gzip' or 'deflate' content-encoding
		* @param[in/out]	context		HttpContext instance to process
		*/
		void applyContentEncoding (HttpContext &context);

		/**
		* Run handlers registered for current directory against current target,
		* returns true if request was completed.
		* @param[in/out]	context		Filled HttpContext instance
		*/
		bool runHandlers (HttpContext& context, const struct DirectorySettings& dirSettings);
		
		void applyMappings (HttpContext& context, const struct DirectorySettings& dirSettings);

		void processDirectFileRequest (HttpContext& context);
		
		void sendFileToClient (HttpContext& context, 
			std::size_t fileSize, 
			string_constref filePath, 
			std::time_t modifyTime,
			bool loadRange);

		void processDirectoryRequest (HttpContext& context, 
			const struct DirectorySettings& dirSettings);

		string formatParentDirRecord (const DirectorySettings& dirSettings, 
			string_constref parentPath);
		string formatHeaderRecord (const DirectorySettings& dirSettings, 
			string_constref virtualPath);
		string formatItemRecord (string_constref itemTemplate,
			string_constref itemUrl,
			string_constref itemName,
			const size_t itemSize,
			std::time_t lastWriteTime);
		string formatFooterRecord (const DirectorySettings& dirSettings, 
			string_constref virtualPath,
			const size_t fileCount, const size_t dirCount, const size_t errCount);

		inline string formatDateTime (const struct tm& dateTime)
		{
			const int buffSize = 20;
			aconnect::char_type buff[buffSize] = {0};

			int cnt = snprintf (buff, buffSize, "%.2d.%.2d.%.4d %.2d:%.2d:%.2d", 
				dateTime.tm_mday,
				dateTime.tm_mon + 1,
				dateTime.tm_year + 1900,
				dateTime.tm_hour,
				dateTime.tm_min,
				dateTime.tm_sec
				);

			return string (buff, aconnect::util::min2(cnt, buffSize) );
		}
	};
}
#endif // AHTTP_SERVER_H
